<template>
  <!--
    3D地球点云可视化容器
    这个div元素将作为Three.js渲染器的挂载点
    ref="container" 允许我们在Vue组件中通过JavaScript访问这个DOM元素
    class="earth-point-cloud-container" 用于应用CSS样式
    显示地球大气层效果和全球陆地点云数据
  -->
  <div ref="container" class="earth-point-cloud-container"></div>
</template>

<script setup lang="ts">
// ==============================================
// 导入必要的库和模块
// ==============================================

// Vue 3的Composition API相关函数：
// - ref: 用于创建响应式引用，可以跟踪DOM元素或数据的变化
// - onMounted: 组件挂载到DOM后执行的回调函数
// - onUnmounted: 组件从DOM卸载前执行的回调函数
import { ref, onMounted, onUnmounted } from 'vue';

// Three.js库：用于创建和渲染3D图形的JavaScript库
// THREE对象包含所有Three.js的核心功能，如场景、相机、渲染器等
import * as THREE from 'three';

// 轨道控制器：允许用户用鼠标拖拽来旋转、平移和缩放3D场景
// 这是Three.js的一个扩展功能，需要单独导入
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

// 导入地球陆地点矩阵数据（JSON格式）
// 数据来源：https://gitee.com/xu-dong422/earth-land-points-generator
// 包含全球陆地点的经纬度坐标和颜色信息，用于在地球表面显示彩色数据点云
import earthLandDotMatrix from '@/assets/json/earth_land_points.json';

// ==============================================
// 变量声明和初始化
// ==============================================

// 使用Vue的ref创建对DOM元素的引用
// 这个容器将用于挂载Three.js的渲染器
const container = ref<HTMLElement | null>(null);

// Three.js相关变量声明
// 这些变量将在初始化过程中被赋值，用于管理3D场景的各个部分
let scene: THREE.Scene | null = null;          // 3D场景，包含所有要渲染的对象
let camera: THREE.PerspectiveCamera | null = null; // 透视相机，定义如何查看3D场景
let renderer: THREE.WebGLRenderer | null = null;   // 渲染器，负责将3D场景绘制到屏幕上
let controls: OrbitControls | null = null;     // 轨道控制器，允许用户交互控制场景
let earthSphere: THREE.Mesh | null = null;     // 地球球体网格（大气层效果）
let animationId: number | null = null;         // 动画帧ID，用于控制动画循环
let points: THREE.Points | null = null;        // 点云对象，表示地球上的数据点
let earthMesh: THREE.Mesh | null = null;       // 透明地球球体网格

// ==============================================
// 数据类型定义
// ==============================================

// 定义地球陆地点数据的接口（TypeScript类型定义）
// 这确保了我们使用的数据具有正确的结构
interface EarthLandDotMatrixData {
  lat: number;    // 纬度：-90°到90°，0°表示赤道
  lng: number;    // 经度：-180°到180°，0°表示本初子午线
  color: string;  // 颜色值：十六进制格式，如'#ff0000'表示红色
}

// 存储地球陆地点数据的变量
let earthLandDotMatrixData: EarthLandDotMatrixData[] | null = null;

// 将导入的JSON数据赋值给我们的变量
earthLandDotMatrixData = earthLandDotMatrix;

// ==============================================
// 着色器代码（用于创建特殊视觉效果）
// ==============================================

// 顶点着色器：处理每个顶点的位置和属性
// 在3D图形中，顶点是构成3D模型的基本点
const vertexShader = `
  // 定义 varying 变量，这些变量可以在顶点着色器和片段着色器之间传递数据
  varying vec3 vVertexWorldPosition;  // 顶点在世界坐标系中的位置
  varying vec3 vVertexNormal;         // 顶点的法线向量（用于光照计算）
  
  void main() {
    // 将法线向量从模型坐标系转换到世界坐标系，并归一化（长度为1）
    vVertexNormal = normalize(normalMatrix * normal);
    
    // 将顶点位置从模型坐标系转换到世界坐标系
    vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
    
    // 设置顶点的最终位置（必须的）
    // 将顶点从模型视图坐标系转换到裁剪坐标系
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`;

// 片段着色器：处理每个像素的颜色
// 决定屏幕上每个像素最终显示什么颜色
const fragmentShader = `
  // uniform 变量：这些变量在着色器执行过程中保持不变，由JavaScript设置
  uniform vec3 color;        // 基础颜色
  uniform float coefficient; // 系数，用于调整效果强度
  uniform float power;       // 幂值，用于调整效果曲线
  
  // varying 变量：从顶点着色器传递过来的数据
  varying vec3 vVertexNormal;         // 顶点的法线向量
  varying vec3 vVertexWorldPosition;  // 顶点在世界坐标系中的位置
  
  void main() {
    // 计算从相机位置到当前顶点的向量（在世界坐标系中）
    vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;
    
    // 将向量从世界坐标系转换到视图坐标系
    vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;
    
    // 归一化向量（使其长度为1），便于计算
    viewCameraToVertex = normalize(viewCameraToVertex);
    
    // 计算基础强度值：
    // 1. 计算法线向量和视线向量的点积（表示它们的方向相似程度）
    // 2. 加上系数值
    // 3. 使用max确保结果不小于0（避免负值）
    float baseIntensity = max(0.0, coefficient + dot(vVertexNormal, viewCameraToVertex));
    
    // 应用幂函数来调整强度曲线（非线性效果）
    float intensity = pow(baseIntensity, power);
    
    // 将强度限制在0到1之间（确保值有效）
    intensity = clamp(intensity, 0.0, 1.0);
    
    // 设置最终像素颜色：
    // - RGB来自uniform颜色
    // - Alpha（透明度）来自计算出的强度值
    gl_FragColor = vec4(color, intensity);
  }
`;

// ==============================================
// 工具函数
// ==============================================

/**
 * 将经纬度坐标转换为3D空间中的位置向量
 * 这个函数用于将地球上的点（用经纬度表示）转换为3D场景中的具体位置
 *
 * @param lat - 纬度：-90°（南极点）到90°（北极点）
 * @param lng - 经度：-180°到180°
 * @param radius - 球体半径（地球的半径）
 * @returns THREE.Vector3 - 3D空间中的位置向量
 */
function latLongToVector3(lat: number, lng: number, radius: number): THREE.Vector3 {
  // 将纬度转换为球坐标系的极角（phi）
  // 90 - lat 是因为：纬度0°（赤道）对应极角90°，纬度90°（北极）对应极角0°
  const phi = (90 - lat) * Math.PI / 180;
  
  // 将经度转换为球坐标系的方位角（theta）
  // lng + 180 是为了将经度从[-180,180]范围转换到[0,360]范围
  const theta = (lng + 180) * Math.PI / 180;

  // 使用球坐标公式计算3D直角坐标系中的位置：
  // x = -r * sin(phi) * cos(theta)
  // y = r * cos(phi)
  // z = r * sin(phi) * sin(theta)
  // 注意：x坐标取负是为了调整方向，使场景看起来更自然
  return new THREE.Vector3(
    -radius * Math.sin(phi) * Math.cos(theta),
    radius * Math.cos(phi),
    radius * Math.sin(phi) * Math.sin(theta),
  );
}

// ==============================================
// 主要功能函数
// ==============================================

/**
 * 初始化Three.js场景
 * 这个函数设置整个3D场景，包括：
 * - 创建场景、相机、渲染器
 * - 添加光源
 * - 创建地球模型
 * - 设置轨道控制器
 * - 创建数据点和星空背景
 */
async function init(): Promise<void> {
  // 确保容器元素存在（组件已挂载到DOM）
  if (!container.value) {return;}

  try {
    // 1. 创建场景
    // 场景是3D对象的容器，所有要渲染的对象都需要添加到场景中
    // 创建Three.js场景对象
    // Scene是Three.js中所有3D对象的容器，类似于一个虚拟世界
    // 所有要渲染的3D对象（网格、灯光、相机等）都需要添加到场景中
    scene = new THREE.Scene();
    
    // 2. 创建透视相机
    // 透视相机模拟人眼视角，近大远小
    const width = container.value.clientWidth;   // 容器宽度
    const height = container.value.clientHeight; // 容器高度
    // 创建透视相机
    // PerspectiveCamera模拟人眼视角，具有近大远小的透视效果
    // 参数说明：
    // - fov: 视野角度（75度），角度越大看到的范围越广
    // - aspect: 宽高比（width/height），保持画面比例不变形
    // - near: 近裁剪面（0.1），距离相机0.1单位以内的物体不渲染
    // - far: 远裁剪面（1000），距离相机1000单位以外的物体不渲染
    camera = new THREE.PerspectiveCamera(
      75,              // 视野角度（FOV）：75度，角度越大看到的范围越广
      width / height,  // 宽高比：保持画面不变形
      0.1,             // 近裁剪面：距离相机0.1单位以内的物体不渲染
      1000,            // 远裁剪面：距离相机1000单位以外的物体不渲染
    );
    // 设置相机位置：沿z轴向后移动500单位
    // 这样相机就能看到整个地球场景，而不是从内部观察
    camera.position.z = 500;
    
    // 3. 创建WebGL渲染器
    // WebGL是一种在浏览器中渲染3D图形的技术
    // 创建WebGL渲染器
    // WebGLRenderer使用WebGL API在canvas元素上渲染3D图形
    // 参数对象包含配置选项：
    renderer = new THREE.WebGLRenderer({
      antialias: true,  // 开启抗锯齿：使3D模型的边缘更平滑，减少锯齿
      alpha: true,      // 开启透明度：允许背景显示，canvas可以有透明背景
    });
    // 设置渲染器尺寸：与容器大小保持一致
    // 确保3D场景正确填充整个容器区域
    renderer.setSize(width, height);
    // 设置像素比例：适配高DPI屏幕（如Retina显示屏）
    // 避免在高分辨率屏幕上出现模糊的渲染效果
    renderer.setPixelRatio(window.devicePixelRatio);
    // 将渲染器的canvas DOM元素添加到容器中
    // 这样Three.js就可以在页面上显示3D内容了
    container.value.appendChild(renderer.domElement);
    
    // 4. 添加光源
    // 创建环境光：AmbientLight均匀照亮场景中的所有物体
    // 参数说明：
    // - color: 光的颜色（0xcccccc，浅灰色）
    // - intensity: 光的强度（0.4，40%强度）
    // 环境光没有方向，模拟全局的环境光照效果
    const ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
    scene.add(ambientLight); // 将环境光添加到场景中
    
    // 创建定向光：DirectionalLight来自特定方向的光源，模拟太阳光
    // 参数说明：
    // - color: 光的颜色（0xffffff，白色）
    // - intensity: 光的强度（0.5，50%强度）
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
    // 设置光源位置：使用position.set(x, y, z)方法
    // 这个位置决定了光的方向，光线从这个位置射向原点(0,0,0)
    directionalLight.position.set(5, 3, 5);
    scene.add(directionalLight); // 将定向光添加到场景中
    
    // 5. 创建地球几何体和材质（用于大气层效果）
    // 创建球体几何体：SphereGeometry定义3D球体的形状
    // 参数说明：
    // - radius: 半径（100单位），决定球体的大小
    // - widthSegments: 宽度分段数（75），分割数越多球体越平滑但性能消耗越大
    // - heightSegments: 高度分段数（75），控制球体垂直方向的分辨率
    const sphereGeometry = new THREE.SphereGeometry(
      100, // 半径：100单位
      75,  // 宽度分段数：分割数越多，球体越平滑但性能消耗越大
      75,  // 高度分段数
    );
    
    // 创建着色器材质：ShaderMaterial使用自定义的GLSL着色器代码
    // 这里用于创建地球大气层的特殊发光效果
    // 参数说明：
    const sphereMaterial = new THREE.ShaderMaterial({
      uniforms: {
        // uniform变量：在着色器执行过程中保持不变的全局变量
        // coefficient: 调整大气层效果的强度系数
        coefficient: { value: 0.7 },
        // color: 大气层的颜色，使用THREE.Color对象表示
        color: {
          value: new THREE.Color(
            0.5294117647058824, // 红色分量：约0.53（RGB: 135/255）
            0.807843137254902,  // 绿色分量：约0.81（RGB: 206/255）
            0.9803921568627451, // 蓝色分量：约0.98（RGB: 250/255）
          ), // 浅蓝色，模拟地球大气层的颜色
        },
        // power: 幂值，用于调整效果曲线的形状
        power: { value: 3.5 },
      },
      vertexShader,    // 顶点着色器代码：处理每个顶点的位置和属性
      fragmentShader,  // 片段着色器代码：处理每个像素的颜色计算
      transparent: true, // 启用透明度：允许材质有透明效果
    });
    
    // 6. 创建地球网格（大气层效果）
    // 创建网格对象：Mesh是几何体和材质的组合
    // 代表一个可以在3D场景中渲染的具体对象
    earthSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
    // 设置缩放比例：稍微放大（1.05倍）
    // 这样大气层球体会比地球球体稍大，形成包裹效果
    earthSphere.scale.set(1.05, 1.05, 1.05);
    scene.add(earthSphere); // 将大气层网格添加到场景中

    // 7. 创建透明地球球体
    // 创建透明地球球体：这是实际的地球模型，位于大气层内部
    // 使用相同的球体几何体参数
    const earthGeometry = new THREE.SphereGeometry(100, 75, 75);
    // 创建Phong材质：MeshPhongMaterial支持高光反射效果
    // 参数说明：
    const earthMaterial = new THREE.MeshPhongMaterial({
      transparent: true, // 启用透明度：允许材质透明
      opacity: 0.6,      // 透明度值：0.6（60%不透明，40%透明）
      color: 0xffffff,   // 颜色：白色（0xffffff）
    });
    // 创建地球网格对象
    earthMesh = new THREE.Mesh(earthGeometry, earthMaterial);
    scene.add(earthMesh); // 将地球网格添加到场景中
    
    // 8. 添加轨道控制器
    // 允许用户用鼠标交互控制场景：旋转、缩放、平移
    if (camera) {
      // 创建轨道控制器：OrbitControls允许用户用鼠标交互控制3D场景
      // 参数说明：
      // - camera: 要控制的相机对象
      // - domElement: 监听事件的DOM元素（通常是渲染器的canvas）
      controls = new OrbitControls(camera, renderer.domElement);
      // 启用阻尼效果：使相机运动更加平滑，有惯性效果
      controls.enableDamping = true;
      // 设置阻尼系数：0.05，值越小阻尼效果越强，运动越平滑
      controls.dampingFactor = 0.05;
      // 设置最小缩放距离：用户不能缩放到距离地球小于150单位
      controls.minDistance = 150;
      // 设置最大缩放距离：用户不能缩放到距离地球大于1000单位
      controls.maxDistance = 1000;
    }
    
    // 9. 创建数据点云
    // 使用地球陆地点阵生成器的数据在地球表面显示彩色点云
    // 数据来源：https://gitee.com/xu-dong422/earth-land-points-generator
    createDataPoints();

    // 10. 添加星空背景
    // 在地球周围创建随机分布的星星
    createStarfield();
  } catch {
    // 错误处理：如果初始化过程中出现错误，这里会捕获
    // 在实际应用中，可以在这里添加错误提示或回退方案
  }
}

/**
 * 创建数据点云
 * 使用地球陆地点阵生成器的数据（https://gitee.com/xu-dong422/earth-land-points-generator）
 * 将JSON数据中的经纬度坐标转换为3D空间中的点，并添加到场景中
 * 每个点包含独立的颜色信息，形成彩色点云效果
 */
function createDataPoints(): void {
  // 确保场景和数据都存在
  if (!scene || !earthLandDotMatrixData) {return;}

  try {
    // 创建缓冲几何体：BufferGeometry高效存储大量顶点数据
    // 与传统的Geometry相比，BufferGeometry性能更好，适合处理大量数据点
    const pointsGeometry = new THREE.BufferGeometry();
    const positions: number[] = []; // 存储所有点的位置数据（每个点需要3个值：x,y,z）
    const colors: number[] = [];    // 存储所有点的颜色数据（每个颜色需要3个值：r,g,b）

    // 遍历所有数据点，将经纬度转换为3D坐标
    earthLandDotMatrixData.forEach(point => {
      // 将经纬度转换为3D空间中的位置
      const position = latLongToVector3(point.lat, point.lng, 100);
      
      // 将位置坐标添加到数组（每个点需要3个值：x, y, z）
      positions.push(position.x, position.y, position.z);

      // 将十六进制颜色字符串转换为Three.js颜色对象
      const color = new THREE.Color(point.color);
      
      // 将颜色值添加到数组（每个颜色需要3个值：r, g, b）
      colors.push(color.r, color.g, color.b);
    });

    // 设置几何体的位置属性：将位置数据添加到几何体中
    // setAttribute方法将数据与几何体关联起来
    // Float32BufferAttribute：使用Float32Array高效存储浮点数数据
    // 参数说明：
    // - 'position': 属性名称，表示顶点位置
    // - new THREE.Float32BufferAttribute(positions, 3):
    //   - positions: 位置数据数组
    //   - 3: 每个顶点需要3个值（x, y, z）
    pointsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
    
    // 设置几何体的颜色属性：将颜色数据添加到几何体中
    pointsGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));

    // 创建点材质：PointsMaterial定义点云的外观和行为
    // 参数说明：
    const pointsMaterial = new THREE.PointsMaterial({
      size: 3,               // 点的大小：3像素
      sizeAttenuation: false, // 禁用大小衰减：点的大小不随距离相机远近而变化
      vertexColors: true,    // 使用顶点颜色：每个点使用各自在几何体中定义的颜色
    });

    // 创建点云对象：Points将几何体和材质组合成可渲染的点云
    points = new THREE.Points(pointsGeometry, pointsMaterial);
    scene.add(points); // 将点云对象添加到场景中
  } catch {
    // 错误处理：如果创建点云过程中出现错误
  }
}

/**
 * 创建星空背景
 * 在地球周围创建随机分布的星星，增强3D场景的真实感
 */
function createStarfield(): void {
  if (!scene) {return;}

  try {
    // 创建缓冲几何体：用于存储星星的位置数据
    const starGeometry = new THREE.BufferGeometry();
    const starCount = 5000; // 星星数量：5000颗
    
    // 创建Float32Array存储位置数据：每个星星需要3个坐标值（x,y,z）
    // 所以总长度是 starCount * 3
    const positions = new Float32Array(starCount * 3);
    
    // 随机生成所有星星的位置
    for (let i = 0; i < starCount; i++) {
      const i3 = i * 3; // 计算当前星星在数组中的起始索引
      
      // 使用球坐标随机生成星星位置（在地球外部）
      const radius = 500 + Math.random() * 500; // 距离：500-1000单位
      const theta = Math.random() * Math.PI * 2; // 方位角：0-2π
      const phi = Math.acos(2 * Math.random() - 1); // 极角：使用反余弦确保均匀分布
      
      // 将球坐标转换为直角坐标
      positions[i3] = radius * Math.sin(phi) * Math.cos(theta);     // x坐标
      positions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta); // y坐标
      positions[i3 + 2] = radius * Math.cos(phi);                   // z坐标
    }
    
    // 设置几何体的位置属性：将星星位置数据添加到几何体中
    // 使用BufferAttribute而不是Float32BufferAttribute，因为数据已经是Float32Array
    starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    
    // 创建星星材质：PointsMaterial定义星星的外观
    const starMaterial = new THREE.PointsMaterial({
      color: 0xffffff,     // 颜色：白色（0xffffff）
      size: 1.5,           // 大小：1.5像素
      sizeAttenuation: true, // 启用大小衰减：远处的星星看起来更小，近处的更大
    });
    
    // 创建星星点云对象：Points将几何体和材质组合
    const stars = new THREE.Points(starGeometry, starMaterial);
    scene.add(stars); // 将星星点云添加到场景中
  } catch {
    // 错误处理：如果创建星空过程中出现错误
  }
}

/**
 * 动画循环函数
 * 这个函数会持续运行，更新场景状态并重新渲染
 * 实现地球自转和其他动画效果
 */
function animate(): void {
  // 确保场景、相机和渲染器都存在
  if (!scene || !camera || !renderer) {return;}
  
  // 请求下一帧动画：使用requestAnimationFrame创建动画循环
  // 这是浏览器提供的API，会在下一次重绘前调用指定的回调函数
  // 返回一个ID，可以用于取消动画帧
  animationId = requestAnimationFrame(animate);
  
  // 地球自转：让地球大气层绕y轴缓慢旋转
  // 使用rotation.y属性控制绕y轴的旋转角度
  if (earthSphere) {
    earthSphere.rotation.y += 0.001; // 每帧旋转0.001弧度（约0.057度）
  }

  // 点云旋转：让数据点也随地球一起旋转，保持同步
  if (points) {
    points.rotation.y += 0.001; // 相同的旋转速度
  }

  // 更新轨道控制器：必须在每一帧调用update()方法
  // 这样阻尼效果和其他控制器功能才能正常工作
  if (controls) {
    controls.update(); // 更新控制器状态（处理阻尼、惯性等效果）
  }
  
  // 渲染场景：将3D场景绘制到屏幕上
  // render方法需要场景和相机作为参数
  renderer.render(scene, camera);
}

/**
 * 处理窗口大小变化
 * 当浏览器窗口大小改变时，调整相机和渲染器的大小
 * 保持3D场景的正确比例和清晰度
 */
function onWindowResize(): void {
  // 确保容器、相机和渲染器都存在
  if (!container.value || !camera || !renderer) {return;}
  
  // 获取容器的新尺寸
  const width = container.value.clientWidth;
  const height = container.value.clientHeight;
  
  // 更新相机的宽高比：确保画面比例正确
  // aspect属性控制相机的视野比例
  camera.aspect = width / height;
  
  // 更新相机的投影矩阵：必须调用此方法 after 修改相机参数
  // updateProjectionMatrix()重新计算相机的投影变换
  camera.updateProjectionMatrix();
  
  // 更新渲染器大小：使渲染器适应新的容器尺寸
  // setSize(width, height)调整渲染器的输出尺寸
  renderer.setSize(width, height);
}

/**
 * 清理Three.js资源
 * 在组件卸载时释放内存，防止内存泄漏
 * 这是非常重要的性能优化步骤
 */
function cleanup(): void {
  // 停止动画循环
  if (animationId) {
    cancelAnimationFrame(animationId);
  }
  
  // 清理轨道控制器：释放控制器占用的资源
  if (controls) {
    // dispose()方法移除事件监听器并清理内部状态
    controls.dispose(); // 释放控制器资源
  }
  
  // 清理渲染器：释放WebGL上下文和相关的GPU资源
  if (renderer) {
    // dispose()方法释放WebGL上下文、着色器程序、缓冲区等
    renderer.dispose(); // 释放WebGL资源
  }
  
  // 清理场景中的所有几何体和材质：手动释放GPU内存
  if (scene) {
    // 使用traverse方法遍历场景中的所有对象
    // traverse(callback)会递归访问场景中的每个对象
    scene.traverse((child) => {
      // 检查对象类型：如果是网格(Mesh)或点云(Points)对象
      if (child instanceof THREE.Mesh || child instanceof THREE.Points) {
        // 释放几何体资源：geometry.dispose()释放顶点缓冲区等
        child.geometry.dispose(); // 释放几何体资源
        
        // 释放材质资源：material.dispose()释放着色器程序等
        // 材质可能是单个Material对象或材质数组
        if (child.material instanceof THREE.Material) {
          // 单个材质：直接调用dispose()
          child.material.dispose();
        } else if (Array.isArray(child.material)) {
          // 材质数组：遍历数组并释放每个材质
          child.material.forEach(material => material.dispose());
        }
      }
    });
  }
  
  // 移除窗口大小变化的事件监听器
  window.removeEventListener('resize', onWindowResize);
}

// ==============================================
// Vue组件生命周期钩子
// ==============================================

// 组件挂载时执行：当Vue组件被添加到DOM中后
onMounted(async() => {
  await init();       // 初始化3D场景
  animate();          // 启动动画循环
  window.addEventListener('resize', onWindowResize); // 监听窗口大小变化
});

// 组件卸载时执行：当Vue组件从DOM中移除前
onUnmounted(() => {
  cleanup(); // 清理资源，防止内存泄漏
});
</script>

<style scoped>
/*
  地球容器样式
  这些样式只应用于当前组件，不会影响其他组件
*/

.earth-point-cloud-container {
  width: 100%;            /* 宽度100%：填充父容器 */
  height: 100vh;          /* 高度100vh：视口高度，占满整个屏幕 */
  background-color: #000011; /* 背景色：深蓝色，模拟太空背景 */
  overflow: hidden;       /* 隐藏溢出内容：防止滚动条出现 */
}
</style>